#include "InStream.h"

#include <string.h>
#include <fan-llvm/core/Core.h>
#include "ArgErr.h"
#include "Buf.h"
#include "Charset.h"
#include "Decimal.h"
#include "Endian.h"
#include "FanInt.h"
#include "Func.h"
#include "IOErr.h"
#include "List.h"
#include "Map.h"
#include "OutStream.h"
#include "Str.h"
#include "StrBuf.h"
#include "Type.h"
#include "UnsupportedErr.h"


static const Int kChunkSize = 4096;

static Int InStream_readHex(InStream* self);

Type* InStream_classType = NULL;
VTable* InStream_classVTable = NULL;


/**********************************************
 *
 * Inline methods
 *
 **********************************************/


/* [inline] UInt8 readByte() */
static inline uint8_t InStream_readByte(InStream* self)
{
  NullableInt x = IN_STREAM_VTABLE(self)->read(self);
  if (VAL_IS_NULL(x))
    exceptionThrow(IOErr_newWithFormattedMsg("Unexpected end of stream"));
  return VAL_GET_INT(x);
}


/**********************************************
 *
 * Methods and fields
 *
 **********************************************/


/* Void make(InStream? in) */
void InStream_make(InStream* self, InStream* in)
{
  Obj_make(VAL_MAKE_REF(self));
  self->in = in;
  self->bigEndian = TRUE;
  self->charset = Charset_utf8();
}


/* Endian endian */
Endian* InStream_endian(InStream* self)
{
  return self->bigEndian ? Endian_big() : Endian_little();
}


/* Endian endian */
void InStream_setEndian(InStream* self, Endian* val)
{
  self->bigEndian = (val == Endian_big());
}


/* Charset charset */
Charset* InStream_charset(InStream* self)
{
  return self->charset;
}


/* Charset charset */
void InStream_setCharset(InStream* self, Charset* val)
{
  self->charset = val;
}


/* Int? read() */
NullableInt InStream_read(InStream* self)
{
  if (self->in == NULL)
    exceptionThrow(UnsupportedErr_newWithFormattedMsg("%r wraps null InStream", Type_qname(Obj_typeof(VAL_MAKE_REF(self)))));
  return IN_STREAM_VTABLE(self->in)->read(self->in);
}


/* Int? readBuf(Buf buf, Int n) */
NullableInt InStream_readBuf(InStream* self, Buf* buf, Int n)
{
  if (self->in == NULL)
    exceptionThrow(UnsupportedErr_newWithFormattedMsg("%r wraps null InStream", Type_qname(Obj_typeof(VAL_MAKE_REF(self)))));
  return IN_STREAM_VTABLE(self->in)->readBuf(self->in, buf, n);
}


/* This unread(Int b) */
InStream* InStream_unread(InStream* self, Int b)
{
  if (self->in == NULL)
    exceptionThrow(UnsupportedErr_newWithFormattedMsg("%r wraps null InStream", Type_qname(Obj_typeof(VAL_MAKE_REF(self)))));
  IN_STREAM_VTABLE(self->in)->unread(self->in, b);
  return self;
}


/* Bool close() */
Bool InStream_close(InStream* self)
{
  if (self->in != NULL)
    return IN_STREAM_VTABLE(self->in)->close(self->in);
  return TRUE;
}


/* Int skip(Int n) */
Int InStream_skip(InStream* self, Int n)
{
  if (self->in != NULL)
    return IN_STREAM_VTABLE(self->in)->skip(self->in, n);

  for (Int i = 0; i < n; i++)
  {
    if (VAL_IS_NULL(IN_STREAM_VTABLE(self)->read(self)))
      return i;
  }
  return n;
}


/* Buf readAllBuf() */
Buf* InStream_readAllBuf(InStream* self)
{
  Buf* buf = NULL;

  ExceptionBlock block = exceptionBegin();
  if (block == ExceptionBlock_Try)
  {
    buf = Buf_make(kChunkSize);
    while (!VAL_IS_NULL(IN_STREAM_VTABLE(self)->readBuf(self, buf, kChunkSize)));
    Buf_flip(buf);
  }
  else if (block == ExceptionBlock_Finally)
  {
    IN_STREAM_VTABLE(self)->close(self);
  }
  exceptionEnd();

  return buf;
}


/* Buf readBufFully(Buf? buf, Int n) */
Buf* InStream_readBufFully(InStream* self, Buf* buf, Int n)
{
  if (buf == NULL)
    buf = Buf_make(n);

  Int total = n;
  Int got = 0;
  while (got < total)
  {
    NullableInt r = IN_STREAM_VTABLE(self)->readBuf(self, buf, total - got);
    if (VAL_IS_NULL(r) || VAL_GET_INT(r) == 0)
      exceptionThrow(IOErr_newWithFormattedMsg("Unexpected end of stream"));
    got += VAL_GET_INT(r);
  }

  Buf_flip(buf);
  return buf;
}


/* Int? peek() */
NullableInt InStream_peek(InStream* self)
{
  NullableInt x = IN_STREAM_VTABLE(self)->read(self);
  if (!VAL_IS_NULL(x))
    IN_STREAM_VTABLE(self)->unread(self, VAL_GET_INT(x));
  return x;
}


/* Int readU1() */
Int InStream_readU1(InStream* self)
{
  uint8_t c = InStream_readByte(self);
  return c;
}


/* Int readS1() */
Int InStream_readS1(InStream* self)
{
  uint8_t c = InStream_readByte(self);
  return (int8_t)c;
}


/* Int readU2() */
Int InStream_readU2(InStream* self)
{
  uint16_t c1 = InStream_readByte(self);
  uint16_t c2 = InStream_readByte(self);
  if (self->bigEndian)
    return c1 << 8 | c2;
  else
    return c2 << 8 | c1;
}


/* Int readS2() */
Int InStream_readS2(InStream* self)
{
  uint16_t c1 = InStream_readByte(self);
  uint16_t c2 = InStream_readByte(self);
  if (self->bigEndian)
    return (int16_t)(c1 << 8 | c2);
  else
    return (int16_t)(c2 << 8 | c1);
}


/* Int readU4() */
Int InStream_readU4(InStream* self)
{
  uint32_t c1 = InStream_readByte(self);
  uint32_t c2 = InStream_readByte(self);
  uint32_t c3 = InStream_readByte(self);
  uint32_t c4 = InStream_readByte(self);
  if (self->bigEndian)
    return c1 << 24 | c2 << 16 | c3 << 8 | c4;
  else
    return c4 << 24 | c3 << 16 | c2 << 8 | c1;
}


/* Int readS4() */
Int InStream_readS4(InStream* self)
{
  uint32_t c1 = InStream_readByte(self);
  uint32_t c2 = InStream_readByte(self);
  uint32_t c3 = InStream_readByte(self);
  uint32_t c4 = InStream_readByte(self);
  if (self->bigEndian)
    return (int32_t)(c1 << 24 | c2 << 16 | c3 << 8 | c4);
  else
    return (int32_t)(c4 << 24 | c3 << 16 | c2 << 8 | c1);
}


/* Int readS8() */
Int InStream_readS8(InStream* self)
{
  uint64_t c1 = InStream_readByte(self);
  uint64_t c2 = InStream_readByte(self);
  uint64_t c3 = InStream_readByte(self);
  uint64_t c4 = InStream_readByte(self);
  uint64_t c5 = InStream_readByte(self);
  uint64_t c6 = InStream_readByte(self);
  uint64_t c7 = InStream_readByte(self);
  uint64_t c8 = InStream_readByte(self);
  if (self->bigEndian)
    return c1 << 56 | c2 << 48 | c3 << 40 | c4 << 32 | c4 << 24 | c4 << 16 | c4 << 8 | c4;
  else
    return c8 << 56 | c7 << 48 | c6 << 40 | c5 << 32 | c4 << 24 | c3 << 16 | c2 << 8 | c1;
}


/* Float readF4() */
Float InStream_readF4(InStream* self)
{
  int32_t v = InStream_readS4(self);
  float* floatPtr = (float*)&v;
  return *floatPtr;
}


/* Float readF8() */
Float InStream_readF8(InStream* self)
{
  int64_t v = InStream_readS8(self);
  double* floatPtr = (double*)&v;
  return *floatPtr;
}


/* Bool readBool() */
Bool InStream_readBool(InStream* self)
{
  return InStream_readByte(self) != 0;
}


/* Decimal readDecimal() */
Decimal* InStream_readDecimal(InStream* self)
{
  return Decimal_fromStr(InStream_readUtf(self), TRUE);
}


/* Str readUtf() */
Str* InStream_readUtf(InStream* self)
{
  // Read two-byte length
  uint16_t len1 = InStream_readByte(self);
  uint16_t len2 = InStream_readByte(self);
  Int utflen = len1 << 8 | len2;

  Char* buf = memoryAlloc(utflen * sizeof(Char)); // Char buffer we read into
  Int bnum = 0, cnum = 0;                           // Byte count, char count

  // Read the chars
  uint32_t c, c2, c3;
  while (bnum < utflen)
  {
    c = InStream_readByte(self);
    bnum++;
    switch (c >> 4)
    {
      case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
        // 0xxxxxxx
        buf[cnum++] = c;
        break;
      case 12: case 13:
        // 110x xxxx  10xx xxxx
        if (bnum >= utflen)
          exceptionThrow(IOErr_newWithFormattedMsg("UTF encoding error"));
        c2 = InStream_readByte(self);
        bnum++;
        if ((c2 & 0xC0) != 0x80)
          exceptionThrow(IOErr_newWithFormattedMsg("UTF encoding error"));
        buf[cnum++] = (((c & 0x1F) << 6) | (c2 & 0x3F));
        break;
      case 14:
        // 1110 xxxx  10xx xxxx  10xx xxxx */
        if (bnum + 1 >= utflen)
          exceptionThrow(IOErr_newWithFormattedMsg("UTF encoding error"));
        c2 = InStream_readByte(self);
        c3 = InStream_readByte(self);
        bnum += 2;
        if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80))
          exceptionThrow(IOErr_newWithFormattedMsg("UTF encoding error"));
        buf[cnum++] = (((c & 0x0F) << 12) | ((c2 & 0x3F) << 6) | ((c3 & 0x3F) << 0));
        break;
      default:
        // 10xx xxxx  1111 xxxx
        exceptionThrow(IOErr_newWithFormattedMsg("UTF encoding error"));
        break;
    }
  }

  // Allocate as Str
  return Str_newFromBuffer(buf, cnum);
}


/* Int? readChar() */
NullableInt InStream_readChar(InStream* self)
{
  if (IN_STREAM_VTABLE(self)->readChar != NULL)
    return IN_STREAM_VTABLE(self)->readChar(self);

  if (self->in != NULL)
    return InStream_readChar(self->in);
  return Charset_decode(self->charset, self);
}


/* This unreadChar(Int b) */
InStream* InStream_unreadChar(InStream* self, Int b)
{
  if (IN_STREAM_VTABLE(self)->unreadChar != NULL)
    return IN_STREAM_VTABLE(self)->unreadChar(self, b);

  if (self->in != NULL)
    InStream_unreadChar(self->in, b);
  else
    Charset_encodeToInstream(self->charset, b, self);
  return self;
}


/* Int? peekChar() */
NullableInt InStream_peekChar(InStream* self)
{
  NullableInt x = InStream_readChar(self);
  if (!VAL_IS_NULL(x))
    InStream_unreadChar(self, VAL_GET_INT(x));
  return x;
}


/* Str readChars(Int n) */
Str* InStream_readChars(InStream* self, Int n)
{
  if (n < 0)
    exceptionThrow(ArgErr_newWithFormattedMsg("readChars n < 0: %i", n));
  if (n == 0)
    return Str_defVal();
  StrBuf* buf = StrBuf_new(256);
  for (Int i = 0; i < n; i++)
  {
    NullableInt x = InStream_readChar(self);
    if (VAL_IS_NULL(x))
      exceptionThrow(IOErr_newWithFormattedMsg("Unexpected end of stream"));
    StrBuf_addChar(buf, VAL_GET_INT(x));
  }
  return StrBuf_toStr(VAL_MAKE_REF(buf));
}


/* Str? readLine(Int? max) */
Str* InStream_readLine_0(InStream* self)
{
  return InStream_readLine(self, VAL_MAKE_INT(kChunkSize));
}


/* Str? readLine(Int? max) */
Str* InStream_readLine(InStream* self, NullableInt max)
{
  // Max limit
  Int maxChars = !VAL_IS_NULL(max) ? VAL_GET_INT(max) : Int_maxVal();
  if (maxChars <= 0)
    return Str_defVal();

  // Read first char, if at end of stream
  NullableInt x = InStream_readChar(self);
  if (VAL_IS_NULL(x))
    return NULL;

  // Loop reading chars until we hit newline combo or end of stream
  StrBuf* buf = StrBuf_new(256);
  while (TRUE)
  {
    Int c = VAL_GET_INT(x);

    // Check for \n, \r\n, or \r
    if (c == '\n')
      break;
    if (c == '\r')
    {
      x = InStream_readChar(self);
      if (!VAL_IS_NULL(x) && VAL_GET_INT(x) != '\n')
        InStream_unreadChar(self, VAL_GET_INT(x));
      break;
    }

    // Append to working buffer
    StrBuf_addChar(buf, c);
    if (StrBuf_size(buf) >= maxChars)
      break;

    // Read next char
    x = InStream_readChar(self);
    if (VAL_IS_NULL(x))
      break;
  }
  return StrBuf_toStr(VAL_MAKE_REF(buf));
}


/* Str? readStrToken(Int? max, |Int->Bool|? c) */
Str* InStream_readStrToken_0(InStream* self)
{
  return InStream_readStrToken(self, VAL_MAKE_INT(kChunkSize), NULL);
}


/* Str? readStrToken(Int? max, |Int->Bool|? c) */
Str* InStream_readStrToken_1(InStream* self, NullableInt max)
{
  return InStream_readStrToken(self, max, NULL);
}


/* Str? readStrToken(Int? max, |Int->Bool|? c) */
Str* InStream_readStrToken(InStream* self, NullableInt max, Func* f)
{
  // Max limit
  Int maxChars = !VAL_IS_NULL(max) ? VAL_GET_INT(max) : Int_maxVal();
  if (maxChars <= 0)
    return Str_defVal();

  // Read first char, if at end of stream
  NullableInt x = InStream_readChar(self);
  if (VAL_IS_NULL(x))
    return NULL;

  // Loop reading chars until our closure returns false
  StrBuf* buf = StrBuf_new(256);
  while (TRUE)
  {
    Int c = VAL_GET_INT(x);

    // Check for termination
    Bool terminate;
    if (f == NULL)
      terminate = Int_isSpace(c);
    else
      terminate = VAL_GET_BOOL(FUNC_VTABLE(f)->call_1(f, VAL_MAKE_INT(c)));
    if (terminate)
    {
      InStream_unreadChar(self, c);
      break;
    }

    // Append to working buffer
    StrBuf_addChar(buf, c);
    if (StrBuf_size(buf) >= maxChars)
      break;

    // Read next char
    x = InStream_readChar(self);
    if (VAL_IS_NULL(x))
      break;
  }
  return StrBuf_toStr(VAL_MAKE_REF(buf));
}


/* Str[] readAllLines() */
List* InStream_readAllLines(InStream* self)
{
  List* list = List_new(Str_classType, 8);

  ExceptionBlock block = exceptionBegin();
  if (block == ExceptionBlock_Try)
  {
    Str* line;
    while ((line = InStream_readLine_0(self)) != NULL)
      List_add(list, VAL_MAKE_REF(line));
  }
  else if (block == ExceptionBlock_Finally)
  {
    IN_STREAM_VTABLE(self)->close(self);
  }
  exceptionEnd();

  return list;
}


/* Void eachLine(|Str->Void| f) */
void InStream_eachLine(InStream* self, Func* f)
{
  ExceptionBlock block = exceptionBegin();
  if (block == ExceptionBlock_Try)
  {
    Str* line;
    while ((line = InStream_readLine_0(self)) != NULL)
      FUNC_VTABLE(self)->call_1(f, VAL_MAKE_REF(line));
  }
  else if (block == ExceptionBlock_Finally)
  {
    IN_STREAM_VTABLE(self)->close(self);
  }
  exceptionEnd();
}


/* Str readAllStr(Bool normalizeNewlines) */
Str* InStream_readAllStr_0(InStream* self)
{
  return InStream_readAllStr(self, TRUE);
}


/* Str readAllStr(Bool normalizeNewlines) */
Str* InStream_readAllStr(InStream* self, Bool normalizeNewlines)
{
  StrBuf* buf = StrBuf_new(kChunkSize);

  ExceptionBlock block = exceptionBegin();
  if (block == ExceptionBlock_Try)
  {
    // Read characters
    Int last = -1;
    while (TRUE)
    {
      NullableInt x = InStream_readChar(self);
      if (VAL_IS_NULL(x))
        break;
      Int c = VAL_GET_INT(x);

      // Normalize newlines and add to buffer
      if (normalizeNewlines)
      {
        if (c == '\r')
          StrBuf_addChar(buf, '\n');
        else if (!(last == '\r' && c == '\n'))
          StrBuf_addChar(buf, c);
        last = c;
      }
      else
        StrBuf_addChar(buf, c);
    }
  }
  else if (block == ExceptionBlock_Finally)
  {
    IN_STREAM_VTABLE(self)->close(self);
  }
  exceptionEnd();

  return StrBuf_toStr(VAL_MAKE_REF(buf));
}


/* Obj? readObj([Str:Obj]? options) */
NullableObj InStream_readObj_0(InStream* self)
{
  return InStream_readObj(self, NULL);
}


/* Obj? readObj([Str:Obj]? options) */
NullableObj InStream_readObj(InStream* self, Map* options)
{
  exceptionThrow(UnsupportedErr_newWithFormattedMsg("sys::InStream.readObj not implemented yet"));
  return VAL_NULL;
}


/* [Str:Str] readProps() */
Map* InStream_readProps(InStream* self)
{
  Map* props = Map_new(Str_classType, Str_classType);

  Charset* origCharset = self->charset;
  self->charset = Charset_utf8();

  ExceptionBlock block = exceptionBegin();
  if (block == ExceptionBlock_Try)
  {
    StrBuf* name = StrBuf_new(16);
    StrBuf* val = StrBuf_new(16);
    Bool hasVal = FALSE;
    Bool inLineComment = FALSE;
    Int inBlockComment = 0;
    Int c =  ' ';
    Int last = ' ';
    Int lineNum = 1;
    NullableInt x;

    while (TRUE)
    {
      last = c;
      x = InStream_readChar(self);
      if (VAL_IS_NULL(x))
        break;
      c = VAL_GET_INT(x);

      // End of line
      if (c == '\n' || c == '\r')
      {
        inLineComment = FALSE;
        if (last == '\r' && c == '\n')
          continue;
        Str* n = Str_trim(StrBuf_toStr(VAL_MAKE_REF(name)));
        if (hasVal)
        {
          Map_add(props, VAL_MAKE_REF(n), VAL_MAKE_REF(StrBuf_toStr(VAL_MAKE_REF(val))));
          StrBuf_clear(name);
          StrBuf_clear(val);
          hasVal = FALSE;
        }
        else if (Str_size(n) > 0)
          exceptionThrow(IOErr_newWithFormattedMsg("Invalid name/value pair [Line %i]", lineNum));
        lineNum++;
        continue;
      }

      // Line comment
      if (inLineComment)
        continue;

      // Block comment
      if (inBlockComment > 0)
      {
        if (last == '/' && c == '*')
          inBlockComment++;
        if (last == '*' && c == '/')
          inBlockComment--;
        continue;
      }

      // Equal
      if (c == '=' && !hasVal)
      {
        hasVal = TRUE;
        continue;
      }

      // Comment
      if (c == '/' && Int_isSpace(last))
      {
        x = InStream_peekChar(self);
        if (VAL_IS_NULL(x))
          break;
        Int peek = VAL_GET_INT(x);
        if (peek == '/')
        {
          inLineComment = TRUE;
          continue;
        }
        if (peek == '*')
        {
          inBlockComment++;
          continue;
        }
      }

      // Escape or line continuation
      if (c == '\\')
      {
        x = InStream_readChar(self);
        if (VAL_IS_NULL(x))
          break;
        Int peek = VAL_GET_INT(x);
        if (peek == 'n')
          c = '\n';
        else if (peek == 'r')
          c = '\r';
        else if (peek == 't')
          c = '\t';
        else if (peek == '\\')
          c = '\\';
        else if (peek == '\r' || peek == '\n')
        {
          // Line continuation
          lineNum++;
          if (peek == '\r')
          {
            x = InStream_readChar(self);
            if (!VAL_IS_NULL(x))
            {
              peek = VAL_GET_INT(x);
              if (peek != '\n')
                InStream_unreadChar(self, peek);
            }
          }
          while (TRUE)
          {
            x = InStream_readChar(self);
            if (!VAL_IS_NULL(x))
            {
              peek = VAL_GET_INT(x);
              if (peek == ' ' || peek == '\t')
                continue;
              InStream_unreadChar(self, peek);
            }
            break;
          }
          continue;
        }
        else if (peek == 'u')
        {
          Int n3 = InStream_readHex(self);
          Int n2 = InStream_readHex(self);
          Int n1 = InStream_readHex(self);
          Int n0 = InStream_readHex(self);
          if ((n3 | n2 | n1 | n0) < 0)
            exceptionThrow(IOErr_newWithFormattedMsg("Invalid hex value for \\uxxxx [Line %i]"));
          c = ((n3 << 12) | (n2 << 8) | (n1 << 4) | n0);
        }
        else
          exceptionThrow(IOErr_newWithFormattedMsg("Invalid escape sequence [Line %i]", lineNum));
      }

      // Normal character
      if (!hasVal)
        StrBuf_addChar(name, c);
      else
        StrBuf_addChar(val, c);
    }

    Str* n = Str_trim(StrBuf_toStr(VAL_MAKE_REF(name)));
    if (hasVal)
      Map_add(props, VAL_MAKE_REF(n), VAL_MAKE_REF(StrBuf_toStr(VAL_MAKE_REF(val))));
    else if (Str_size(n) > 0)
      exceptionThrow(IOErr_newWithFormattedMsg("Invalid name/value pair [Line %i]", lineNum));
  }
  else if (block == ExceptionBlock_Finally)
  {
    IN_STREAM_VTABLE(self)->close(self);
    self->charset = origCharset;
  }
  exceptionEnd();

  return props;
}


/* [private] Int readHex() */
static Int InStream_readHex(InStream* self)
{
  NullableInt x = InStream_readChar(self);
  if (VAL_IS_NULL(x))
    return -1;
  Int c = VAL_GET_INT(x);
  if ('0' <= c && c <= '9')
    return c - '0';
  if ('a' <= c && c <= 'f')
    return c - 'a' + 10;
  if ('A' <= c && c <= 'F')
    return c - 'A' + 10;
  return -1;
}


/* Int pipe(OutStream out, Int? n, Bool close) */
Int InStream_pipe_1(InStream* self, OutStream* out)
{
  return InStream_pipe(self, out, VAL_NULL, TRUE);
}


/* Int pipe(OutStream out, Int? n, Bool close) */
Int InStream_pipe_2(InStream* self, OutStream* out, NullableInt n)
{
  return InStream_pipe(self, out, n, TRUE);
}


/* Int pipe(OutStream out, Int? n, Bool close) */
Int InStream_pipe(InStream* self, OutStream* out, NullableInt toPipe, Bool close)
{
  Int total = 0;

  ExceptionBlock block = exceptionBegin();
  if (block == ExceptionBlock_Try)
  {
    Int bufSize = kChunkSize;
    Buf* buf = Buf_make(bufSize);
    if (VAL_IS_NULL(toPipe))
    {
      while (TRUE)
      {
        NullableInt n = IN_STREAM_VTABLE(self)->readBuf(self, Buf_clear(buf), bufSize);
        if (VAL_IS_NULL(n))
          break;
        OUT_STREAM_VTABLE(out)->writeBuf(out, Buf_flip(buf), Buf_remaining(buf));
        total += VAL_GET_INT(n);
      }
    }
    else
    {
      Int toPipeVal = VAL_GET_INT(toPipe);
      while (total < toPipeVal)
      {
        if (toPipeVal - total < bufSize)
          bufSize = toPipeVal - total;
        NullableInt n = IN_STREAM_VTABLE(self)->readBuf(self, Buf_clear(buf), bufSize);
        if (VAL_IS_NULL(n))
          exceptionThrow(IOErr_newWithFormattedMsg("Unexpected end of stream"));
        OUT_STREAM_VTABLE(out)->writeBuf(out, Buf_flip(buf), Buf_remaining(buf));
        total += VAL_GET_INT(n);
      }
    }
  }
  else if (block == ExceptionBlock_Finally)
  {
    if (close)
      IN_STREAM_VTABLE(self)->close(self);
  }
  exceptionEnd();

  return total;
}


/**********************************************
 *
 * Type-system functions
 *
 **********************************************/


Type* InStream_initType(void)
{
  if (InStream_classType == NULL)
  {
    Type* baseType = Obj_initType();
    VTable* vtable = InStream_initVTable();
    Type* type = Type_new("InStream", baseType, vtable, sizeof(InStream), sizeof(InStreamVTable));
    Type_registerCtorImplementation(type, "make", InStream_make, 1);
    Type_registerMethodImplementation(type, "close", InStream_close, 0);
    Type_registerMethodImplementation(type, "eachLine", InStream_eachLine, 1);
    Type_registerMethodImplementation(type, "peek", InStream_peek, 0);
    Type_registerMethodImplementation(type, "peekChar", InStream_peekChar, 0);
    Type_registerMethodImplementation(type, "pipe", InStream_pipe_1, 1);
    Type_registerMethodImplementation(type, "pipe", InStream_pipe_2, 2);
    Type_registerMethodImplementation(type, "pipe", InStream_pipe, 3);
    Type_registerMethodImplementation(type, "read", InStream_read, 0);
    Type_registerMethodImplementation(type, "readAllBuf", InStream_readAllBuf, 0);
    Type_registerMethodImplementation(type, "readAllLines", InStream_readAllLines, 0);
    Type_registerMethodImplementation(type, "readAllStr", InStream_readAllStr_0, 0);
    Type_registerMethodImplementation(type, "readAllStr", InStream_readAllStr, 1);
    Type_registerMethodImplementation(type, "readBool", InStream_readBool, 0);
    Type_registerMethodImplementation(type, "readBuf", InStream_readBuf, 2);
    Type_registerMethodImplementation(type, "readBufFully", InStream_readBufFully, 2);
    Type_registerMethodImplementation(type, "readChar", InStream_readChar, 0);
    Type_registerMethodImplementation(type, "readChars", InStream_readChars, 1);
    Type_registerMethodImplementation(type, "readDecimal", InStream_readDecimal, 0);
    Type_registerMethodImplementation(type, "readF4", InStream_readF4, 0);
    Type_registerMethodImplementation(type, "readF8", InStream_readF8, 0);
    Type_registerMethodImplementation(type, "readLine", InStream_readLine_0, 0);
    Type_registerMethodImplementation(type, "readLine", InStream_readLine, 1);
    Type_registerMethodImplementation(type, "readObj", InStream_readObj_0, 0);
    Type_registerMethodImplementation(type, "readObj", InStream_readObj, 1);
    Type_registerMethodImplementation(type, "readProps", InStream_readProps, 0);
    Type_registerMethodImplementation(type, "readS1", InStream_readS1, 0);
    Type_registerMethodImplementation(type, "readS2", InStream_readS2, 0);
    Type_registerMethodImplementation(type, "readS4", InStream_readS4, 0);
    Type_registerMethodImplementation(type, "readS8", InStream_readS8, 0);
    Type_registerMethodImplementation(type, "readStrToken", InStream_readStrToken_0, 0);
    Type_registerMethodImplementation(type, "readStrToken", InStream_readStrToken_1, 1);
    Type_registerMethodImplementation(type, "readStrToken", InStream_readStrToken, 2);
    Type_registerMethodImplementation(type, "readU1", InStream_readU1, 0);
    Type_registerMethodImplementation(type, "readU2", InStream_readU2, 0);
    Type_registerMethodImplementation(type, "readU4", InStream_readU4, 0);
    Type_registerMethodImplementation(type, "readUtf", InStream_readUtf, 0);
    Type_registerMethodImplementation(type, "skip", InStream_skip, 1);
    Type_registerMethodImplementation(type, "unread", InStream_unread, 1);
    Type_registerMethodImplementation(type, "unreadChar", InStream_unreadChar, 1);
    Type_registerFieldImplementation(type, "charset", InStream_charset, InStream_setCharset);
    Type_registerFieldImplementation(type, "endian", InStream_endian, InStream_setEndian);
    InStream_classType = type;
    InStream_initStatic();
  }
  return InStream_classType;
}


VTable* InStream_initVTable(void)
{
  if (InStream_classVTable == NULL)
  {
    VTable* vtable = memoryAlloc(sizeof(InStreamVTable));
    memcpy(vtable, Obj_initVTable(), sizeof(FanObjVTable));
    ((InStreamVTable*)vtable)->close = InStream_close;
    ((InStreamVTable*)vtable)->read = InStream_read;
    ((InStreamVTable*)vtable)->readBuf = InStream_readBuf;
    ((InStreamVTable*)vtable)->skip = InStream_skip;
    ((InStreamVTable*)vtable)->unread = InStream_unread;
    ((InStreamVTable*)vtable)->readChar = NULL;
    ((InStreamVTable*)vtable)->unreadChar = NULL;
    InStream_classVTable = vtable;
  }
  return InStream_classVTable;
}


void InStream_initStatic(void)
{
}
